﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Threading;

namespace Microsoft.Coding4Fun.TweetCraft.Twitter
{
    /// <summary>
    /// Implements the TweetCraft Twitter Dispatcher Service using Tweet#.
    /// </summary>
    public sealed class TwitterDispatcherService : TwitterService, ITwitterDispatcherService, IDisposable
    {
        private const string TweetUrlFormat = "http://twitter.com/{0}/status/{1}";
        private const string PictureFilter = "*.jpg";

        private readonly Queue<Tweet> outgoingTweets = new Queue<Tweet>();
        private readonly Queue<string> outgoingPictures = new Queue<string>();

        private readonly ITwitterClient twitterClient;

        private FileSystemWatcher pictureWatcher;
        private BackgroundWorker tweetDispatcher;

        /// <summary>
        /// Initializes a new instance of the <see cref="TwitterDispatcherService"/> class.
        /// </summary>
        /// <param name="twitterClient">The Twitter service.</param>
        public TwitterDispatcherService(ITwitterClient twitterClient)
        {
            if (twitterClient == null)
                throw new ArgumentNullException("twitterClient");

            this.twitterClient = twitterClient;

            this.InitializePictureWatcher();
            this.InitializeTweetDispatcher();

            // Initialize defaults
            this.AutoTweetPictures = false;
            this.DispatchPeriod = TimeSpan.FromSeconds(5);
        }

        private void InitializePictureWatcher()
        {
            pictureWatcher = new FileSystemWatcher
                             {
                                 Filter = PictureFilter
                             };

            pictureWatcher.Created += OnPictureCreated;
        }

        private void InitializeTweetDispatcher()
        {
            tweetDispatcher = new BackgroundWorker()
                              {
                                  WorkerSupportsCancellation = true
                              };

            tweetDispatcher.DoWork += OnDoTweetDispatcherWork;
            tweetDispatcher.RunWorkerAsync();
        }

        /// <summary>
        /// Queues a tweet for adding to the user's Twitter feed.
        /// </summary>
        /// <param name="tweet">The tweet to queue.</param>
        public void QueueTweet(Tweet tweet)
        {
            if (tweet == null)
                throw new ArgumentNullException("tweet");

            lock (outgoingTweets)
            {
                // Enqueue the tweet to the queue of outgoing tweets
                outgoingTweets.Enqueue(tweet);

                Trace.TraceInformation("Queued tweet with message '{0}' for sending.", tweet.Message);
            }
        }

        /// <summary>
        /// Queues a collection of tweets for adding to the user's Twitter feed.
        /// </summary>
        /// <param name="tweets">The tweets to queue.</param>
        public void QueueTweets(IEnumerable<Tweet> tweets)
        {
            if (tweets == null)
                throw new ArgumentNullException("tweets");

            lock (outgoingTweets)
            {
                foreach (Tweet tweet in tweets)
                    this.QueueTweet(tweet);
            }
        }

        /// <summary>
        /// Adds a picture to the user's Twitter feed without any message.
        /// </summary>
        /// <param name="picturePath">The path to the picture.</param>
        public void QueuePicture(string picturePath)
        {
            if (picturePath == null)
                throw new ArgumentNullException("picturePath");

            lock (outgoingPictures)
            {
                // Enqueue the path of the picture to the queue of outgoing pictures
                outgoingPictures.Enqueue(picturePath);

                Trace.TraceInformation("Queued picture at {0} for sending.", picturePath);
            }
        }

        /// <summary>
        /// Gets or sets the folder where pictures are stored. AutoTweeting enables adding
        /// pictures that show up in this folder.
        /// </summary>
        public string PictureDirectory
        {
            get { return pictureWatcher.Path; }
            set
            {
                // Make sure the directory exists so that we can start watching it
                if (!Directory.Exists(value))
                    Directory.CreateDirectory(value);

                pictureWatcher.Path = value;
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether to enable AutoTweeting of pictures.
        /// </summary>
        public bool AutoTweetPictures
        {
            get { return pictureWatcher.EnableRaisingEvents; }
            set { pictureWatcher.EnableRaisingEvents = value; }
        }

        /// <summary>
        /// Gets or sets the amount of time between tweet and picture dispatches.
        /// </summary>
        public TimeSpan DispatchPeriod { get; set; }
        
        /// <summary>
        /// Raised when a picture has been AutoTweeted or a tweet has been sent.
        /// </summary>
        public event EventHandler<TweetEventArgs> Tweeted;

        private void RaiseTweeted(TweetEventArgs e)
        {
            if (Tweeted != null)
                Tweeted(this, e);
        }

        private void OnTweeted(Tweet tweet)
        {
            // Determine the url to the tweet, if the user is available
            Uri tweetUrl = tweet.User != null  && tweet.User.ScreenName != null ? new Uri(String.Format(CultureInfo.InvariantCulture, TweetUrlFormat, tweet.User.ScreenName, tweet.Id)) : null;

            // Raise the Tweeted event
            this.RaiseTweeted(new TweetEventArgs(TweetSource.TweetCraft, tweet, tweetUrl));
        }

        private void OnAutoTweetedPicture(Tweet tweet)
        {
            // Determine the url to the tweet, if the user is available
            Uri tweetUrl = tweet.User != null && tweet.User.ScreenName != null ? new Uri(String.Format(CultureInfo.InvariantCulture, TweetUrlFormat, tweet.User.ScreenName, tweet.Id)) : null;

            // Raise the Tweeted event
            this.RaiseTweeted(new TweetEventArgs(TweetSource.AutoTweetScreenshot, tweet, tweetUrl));
        }

        private void OnPictureCreated(object sender, FileSystemEventArgs e)
        {
            // Double-check that AutoTweeting is still enabled
            if (this.AutoTweetPictures)
            {
                Trace.TraceInformation("A new picture has been created, AutoTweeting picture...");

                // Queue the picture for tweeting
                this.QueuePicture(e.FullPath);
            }
        }

        private void OnDoTweetDispatcherWork(object sender, DoWorkEventArgs e)
        {
            while (!tweetDispatcher.CancellationPending)
            {
                // Sleep the worker thread to throttle tweeting
                Thread.Sleep(this.DispatchPeriod);

                try
                {
                    // Check the outgoing tweet queue for tweets
                    Tweet tweet = null;
                    lock (outgoingTweets)
                    {
                        if (outgoingTweets.Count > 0)
                            tweet = outgoingTweets.Dequeue();
                    }

                    // Check whether a tweet was queued and add it to Twitter
                    if (tweet != null)
                    {
                        this.AddTweet(tweet);
                        continue;
                    }

                    // Check the outgoing picture queue for pictures to be tweeted
                    string picturePath = null;
                    lock (outgoingPictures)
                    {
                        if (outgoingPictures.Count > 0)
                            picturePath = outgoingPictures.Dequeue();
                    }

                    // Check whether a picture was queued and upload it to TwitPic
                    if (picturePath != null)
                        this.UploadAndPostPicture(picturePath);
                }
                catch (TwitterException exception)
                {
                    // Report the error
                    this.OnError(exception);
                }
            }
        }

        private void AddTweet(Tweet tweetToAdd)
        {
            using (TraceScope scope = TraceScope.Information("Sending tweet using Twitter API..."))
            {
                // Add the tweet using TwitterNet
                Tweet tweet = twitterClient.AddTweet(tweetToAdd.Message);

                scope.Complete("Tweet sent.");

                // Raise Tweeted event with tweet
                this.OnTweeted(tweet);
            }

        }

        private void UploadAndPostPicture(string picturePath)
        {
            if (picturePath == null)
                throw new ArgumentNullException("picturePath");

            using (TraceScope scope = TraceScope.Information("Sending picture using TwitPic API..."))
            {
                // Upload and post the picture using TwitPic
                Tweet tweet = twitterClient.UploadAndPost(picturePath, Strings.TwitPicUploadAndPostMessage);

                scope.Complete("Picture sent.");

                // Raise AutoTweeted event with the tweet
                this.OnAutoTweetedPicture(tweet);
            }

        }

        /// <summary>
        /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
        /// </summary>
        public void Dispose()
        {
            if (pictureWatcher != null)
                pictureWatcher.Dispose();

            if (tweetDispatcher != null)
                tweetDispatcher.Dispose();

            GC.SuppressFinalize(this);
        }
    }
}
